﻿namespace ClearLines.Bumblebee.Tests

open ClearLines.Bumblebee
open System
open System.Collections.Concurrent
open NUnit.Framework
open FsUnit

[<TestFixture>]
type HiveTests() =

   [<Test>]
   member test.``evaluate should return solution and its quality``() =
      // Arrange
      let solution = "Solution"
      let qualityOf string = 42.0

      // Act
      let result = solution |> Hive.evaluate qualityOf

      // Assert
      result.Definition |> should equal solution
      result.Quality |> should equal 42.0

   [<Test>]
   member test.``solutionOf bee should return solution for Scout bee``() =
      // Arrange
      let solution = { Definition = "Solution"; Quality = 42.0 }
      let bee = Scout(solution)

      // Act
      let result = Hive.solutionOf bee

      // Assert
      result |> should equal solution

   [<Test>]
   member test.``solutionOf bee should return solution for Active bee``() =
      // Arrange
      let solution = { Definition = "Solution"; Quality = 42.0 }
      let bee = Active(solution, 100)

      // Act
      let result = Hive.solutionOf bee

      // Assert
      result |> should equal solution

   [<Test>]
   member test.``When candidate quality is better and probability is below conviction level solution should become candidate``() =
      // Arrange
      let original = { Definition = "Original"; Quality = 0.0 }
      let candidate = { Definition = "Candidate"; Quality = 42.0 }
      let convince() = true

      // Act
      let result = original |> Hive.updateWith candidate convince

      // Assert
      result |> should equal candidate

   [<Test>]
   member test.``When candidate quality is worse and probability is below conviction level solution should stay original``() =
      // Arrange
      let original = { Definition = "Original"; Quality = 42.0 }
      let candidate = { Definition = "Candidate"; Quality = 0.0 }
      let convince() = true

      // Act
      let result = original |> Hive.updateWith candidate convince

      // Assert
      result |> should equal original

   [<Test>]
   member test.``When candidate quality is better and probability is above conviction level solution should stay original``() =
      // Arrange
      let original = { Definition = "Original"; Quality = 42.0 }
      let candidate = { Definition = "Candidate"; Quality = 0.0 }
      let convince() = false

      // Act
      let result = original |> Hive.updateWith candidate convince

      // Assert
      result |> should equal original

   [<Test>]
   member test.``When candidate quality is worse and probability is above conviction level solution should stay original``() =
      // Arrange
      let original = { Definition = "Original"; Quality = 42.0 }
      let candidate = { Definition = "Candidate"; Quality = 0.0 }
      let convince() = false

      // Act
      let result = original |> Hive.updateWith candidate convince

      // Assert
      result |> should equal original

   [<Test>]
   member test.``searchResult should pick new better solution when no mistake is made``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 0.0 }
      let better = { Definition = "Better"; Quality = 42.0 }
      let mistake() = false

      // Act
      let result = Hive.searchResult mistake current better

      // Assert
      result |> should equal better

   [<Test>]
   member test.``searchResult should pick original worse solution when mistake is made``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 0.0 }
      let better = { Definition = "Better"; Quality = 42.0 }
      let mistake() = true

      // Act
      let result = Hive.searchResult mistake current better

      // Assert
      result |> should equal current

   [<Test>]
   member test.``searchResult should pick original better solution when no mistake is made``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 42.0 }
      let worse = { Definition = "Worse"; Quality = 0.0 }
      let mistake() = false

      // Act
      let result = Hive.searchResult mistake current worse

      // Assert
      result |> should equal current

   [<Test>]
   member test.``searchResult should pick new worse solution when mistake is made``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 42.0 }
      let worse = { Definition = "Worse"; Quality = 0.0 }
      let mistake() = true

      // Act
      let result = Hive.searchResult mistake current worse

      // Assert
      result |> should equal worse

   [<Test>]
   member test.``redirect Scout bee should do nothing``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 42.0 }
      let selected = { Definition = "Inactive"; Quality = 0.0 }  

      let maxTrips = 10
      let replacement() = selected

      let scout = Scout(current)

      // Act
      let result = scout |> Hive.redirect replacement maxTrips

      // Assert
      result |> should equal scout

   [<Test>]
   member test.``redirect Active bee should do nothing when trips are under limit``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 42.0 }
      let selected = { Definition = "Inactive"; Quality = 0.0 }  

      let maxTrips = 10
      let replacement() = selected

      let active = Active(current, maxTrips)

      // Act
      let result = active |> Hive.redirect replacement maxTrips

      // Assert
      Hive.solutionOf result |> should equal current

   [<Test>]
   member test.``redirect Active bee should pick inactive solution when trips are over limit``() =
      // Arrange
      let current = { Definition = "Current"; Quality = 42.0 }
      let selected = { Definition = "Inactive"; Quality = 0.0 }  

      let maxTrips = 10
      let replacement() = selected

      let active = Active(current, maxTrips + 1)

      // Act
      let result = active |> Hive.redirect replacement maxTrips

      // Assert
      Hive.solutionOf result |> should equal selected

   [<Test>]
   member test.``initialize should create Active Bees based on configuration``() =
      // Arrange
      let config = { ActiveBees = 3; ScoutBees = 0; InactiveBees = 0; ProbaMistake = 0.01; ProbaConvince = 0.7; Trips = 100 }
      let generator = fun () -> { Definition = "Solution"; Quality = 42.0 }

      // Act
      let searchers = Hive.initialize config generator

      // Assert      
      Seq.toList(searchers) |> should haveLength config.ActiveBees

   [<Test>]
   member test.``initialize should create Scout Bees based on configuration``() =
      // Arrange
      let config = { ActiveBees = 0; ScoutBees = 3; InactiveBees = 0; ProbaMistake = 0.01; ProbaConvince = 0.7; Trips = 100 }
      let generator = fun () -> { Definition = "Solution"; Quality = 42.0 }

      // Act
      let searchers = Hive.initialize config generator

      // Assert      
      Seq.toList(searchers) |> should haveLength config.ScoutBees

   [<Test>]
   member test.``initialState should create one Solution per Inactive``() =
      // Arrange
      let config = { ActiveBees = 0; ScoutBees = 0; InactiveBees = 3; ProbaMistake = 0.0; ProbaConvince = 0.0; Trips = 0 }

      let s1 = { Definition = "Solution1"; Quality = 42.0 }
      let s2 = { Definition = "Solution2"; Quality = 100.0 }
      let s3 = { Definition = "Solution3"; Quality = 0.0 }

      let calls = ref 0
      let generator = fun () -> 
         incr calls
         match calls.Value with
         | 1 -> s1
         | 2 -> s2
         | 3 -> s3
         | _ -> failwith "Setup for 3 calls only"

      // Act
      let state = Hive.initialState config generator
      let inactives = snd state

      // Assert      
      inactives |> should haveLength config.InactiveBees
      inactives |> should contain s1
      inactives |> should contain s2
      inactives |> should contain s3

   [<Test>]
   member test.``initialState best should be best of initial Solutions``() =
      // Arrange
      let config = { ActiveBees = 0; ScoutBees = 0; InactiveBees = 3; ProbaMistake = 0.0; ProbaConvince = 0.0; Trips = 0 }

      let s1 = { Definition = "Solution1"; Quality = 42.0 }
      let s2 = { Definition = "Solution2"; Quality = 100.0 }
      let s3 = { Definition = "Solution3"; Quality = 0.0 }

      let calls = ref 0
      let generator = fun () -> 
         incr calls
         match calls.Value with
         | 1 -> s1
         | 2 -> s2
         | 3 -> s3
         | _ -> failwith "Setup for 3 calls only"
 
      // Act
      let state = Hive.initialState config generator
      let best = fst state

      // Assert      
      best |> should equal s2

   [<Test>]
   member test.``search for a Scout should generate new Solution``() =
      // Arrange
      let config = { ActiveBees = 0; ScoutBees = 0; InactiveBees = 0; ProbaMistake = 0.0; ProbaConvince = 0.0; Trips = 0 }

      let generate = fun (rng: Random) -> "GENERATOR"
      let mutate = fun (rng: Random) (s: string) -> failwith "not setup"
      let evaluate = fun (s: string) -> 100.0

      let problem = new Problem<string>(generate, mutate, evaluate)
      let initial = { Definition = "Solution"; Quality = 42.0 }

      let scout = Scout(initial)
      let rng = new Random()

      // Act
      let returning = Hive.search rng problem config scout

      // Assert      
      Hive.solutionOf(returning) |> should equal { Definition = "GENERATOR"; Quality = 100.0 }